<template>
  <div class="clearfix">
    <!-- 上传多个文件 -->
    <template v-if="multiple">
      <el-upload
        action="#"
        :multiple="true"
        :list-type="listType"
        :before-upload="handleBeforeUpload"
        :on-error="handleError"
        :on-preview="handlePreview"
        :on-remove="handleRemove"
        :file-list="fileUrl"
        :http-request="handleCustomFileUpload"
        :limit="maxnum < 1 ? null : maxnum"
        :on-exceed="handleExceed"
      >
        <div v-if="listType === 'picture-card'" class="upload-text">
          <i class="el-icon-plus"></i>
          <p>{{ this.showText }}</p>
        </div>
        <el-button v-else icon="el-icon-plus">
          {{ this.showText }}
        </el-button>
      </el-upload>

      <el-dialog
        :visible="dialogPreview !== null"
        @close="dialogPreview = null"
      >
        <img
          width="100%"
          v-if="dialogPreview"
          :src="dialogPreview"
          alt="example"
        />
      </el-dialog>
    </template>

    <!-- 上传单个文件 -->
    <template v-else>
      <el-upload
        :multiple="false"
        :action="uploadApiUrl"
        :before-upload="handleBeforeUpload"
        :on-success="handleSuccess"
        :on-error="handleError"
        :on-change="handleChange"
        :list-type="listType"
        :show-file-list="false"
      >
        <img
          class="thumb"
          v-if="fileUrl.respUrl && listType === 'picture-card'"
          :src="fileUrl.url"
          alt="avatar"
        />
        <template v-else>
          <div v-if="listType === 'picture-card'" class="upload-text">
            <i :class="showIcon"></i>
            <p>{{ this.showText }}</p>
          </div>
          <el-button v-else :icon="showIcon"> {{ this.showText }} </el-button>
        </template>
        <!-- <div slot="tip">只能上传jpg/png文件，且不超过500kb</div> -->
      </el-upload>
      <label v-if="listType !== 'picture-card' && fileUrl.name">
        {{ fileUrl.name }}
      </label>
    </template>
  </div>
</template>

<script>
import axios from "@/axios";
export default {
  name: "ElemeFileUpload",
  props: {
    // 表单校验用字段
    value: [String, Array, Object],
    //是否允许上传多个文件
    multiple: { type: Boolean, default: false },
    //多选时最多上传的文件数量(-1表示不限制)
    maxnum: {
      type: Number,
      default: -1,
      validator: function (value) {
        if (value === -1) return true;
        return value > 0;
      },
    },
    listType: {
      type: String,
      default: "picture-card",
      validator: function (value) {
        return ["picture-card", "picture", "text"].indexOf(value) !== -1;
      },
    },
    //显示文本
    showText: { type: String, default: "Upload" },
    //是否验证上传文件格式
    verifyFile: { type: Boolean, default: true },
    //允许上传的图片宽高(限制上传图片宽高) {width:width,height:height}
    size: {
      type: Object,
      validator: function (value) {
        //空对象
        if (Object.keys(value).length < 1) return false;
        let isnum = true;
        for (const key in value) {
          if (key === "width" || key === "height") {
            isnum = Number.isFinite(value[key]);
            if (!isnum) break;
          }
        }
        return isnum;
      },
    },
    //充许上传的文件类型
    fileTypes: {
      type: Array,
      default() {
        return this.fileTypes || this.$Conf.uploadFileType || [];
      },
      validator: function (value) {
        value = (value || []).filter((item) => item);
        return value.length > 0;
      },
    },
    //允许上传的文件最大大小
    fileMaxSize: {
      type: Number,
      default() {
        return this.fileMaxSize || this.$Conf.uploadFileMaxSize || 0;
      },
      validator: function (value) {
        return value > 1;
      },
    },
  },
  model: { prop: "value", event: "change" },
  data() {
    return {
      fileUrl: this.multiple ? [] : {},
      loading: false,
      dialogPreview: null,
    };
  },
  watch: {
    //监听父组件传递过来的文件信息
    value: {
      deep: true,
      immediate: false,
      handler: function (newVal, oldVal) {
        if (!newVal) {
          this.fileUrl = this.multiple ? [] : {};
          return false;
        }
        //上传单个文件赋值
        if (!this.multiple) {
          if (typeof newVal !== "string") return false;
          if (this.fileUrl.respUrl === newVal) return false;
          this.fileUrl = {
            uid: new Date().getTime(),
            name: newVal,
            url: `${envConf.imgPreviewUrl}${newVal}`,
            respUrl: newVal,
          };
          return false;
        }
        //上传多个文件赋值
        if (!Array.isArray(newVal)) {
          if (typeof newVal !== "string") return false;
          newVal = newVal.split("|");
        }
        newVal.forEach((item, index) => {
          if (typeof item !== "string") return true;
          const newFileUrl = this.fileUrl.find((x) => x.respUrl === item);
          if (newFileUrl) return true;
          this.fileUrl.push({
            uid: new Date().getTime() + item.length + index,
            name: item,
            url: `${envConf.imgPreviewUrl}${item}`,
            respUrl: item,
          });
        });
      },
    },

    //监听数据变化，及时提交给父组件
    fileUrl: {
      deep: true,
      immediate: false,
      handler: function (newVal, oldVal) {
        if (!newVal) return false;
        if (this.multiple && newVal.length < 1) newVal = null;
        if (!this.multiple && Object.keys(newVal) < 1) newVal = null;
        this.$emit("change", newVal);
      },
    },
  },
  computed: {
    uploadApiUrl: function () {
      if (!envConf.fileUploadUrl) {
        throw new Error("请先配置文件上传接口地址");
      }
      return envConf.fileUploadUrl;
    },
    showIcon: function () {
      return `el-icon-${this.loading ? "loading" : "plus"}`;
    },
    isVerify: function () {
      const types = (this.fileTypes || []).filter((item) => item);
      if (this.verifyFile && types.length < 1 && this.fileMaxSize < 1)
        return false;
      return true;
    },
  },
  methods: {
    handleBeforeUpload(file) {
      return new Promise(async (resolve, reject) => {
        try {
          const err = await this.handleVerifyFile(file);
          if (err) throw new Error(err);
          return resolve(true);
        } catch (error) {
          this.$message.error(error + "");
          return reject(false);
        }
      });
    },
    handleSuccess(response, file, fileList) {
      if (!response) return false;
      if (!(response.serveStatus === 200 && response.status === 1)) {
        this.$message.error(response.message || `${file.name}上传失败`);
        return false;
      }
      const resData = {
        name: file.name,
        size: file.size,
        uid: file.uid,
        url: file.url,
        respUrl: response.data,
      };
      //显示上传多个文件
      if (this.multiple) {
        this.fileUrl.push(resData);
        return false;
      }
      //显示上传单个文件
      this.fileUrl = resData;
    },
    handleError(err, file, fileList) {
      let hint = null;
      try {
        if (typeof err === "string") hint = err;
        if (typeof err === "object") {
          hint = JSON.parse(err.message).message;
        }
      } catch (e) {}
      file.status = "fail";
      this.loading = false;
      this.$message.error(hint || `${file.name}上传失败`);
    },
    handleChange(file, fileList) {
      if (this.multiple) return false;
      if (file.status === "ready") this.loading = true;
      else this.loading = false; // ready success、fail
    },
    handleRemove(file, fileList) {
      file.status = "removed";
      const templist = this.fileUrl || [];
      const existFile = templist.find((x) => x.uid === file.uid);
      if (existFile) {
        const index = templist.findIndex((x) => x.uid === file.uid);
        templist.splice(index, 1);
      }
      this.fileUrl = templist;
    },
    handlePreview(file) {
      this.dialogPreview = file.url;
    },
    //验证上传文件
    async handleVerifyFile(file) {
      // 不需要验证上传的文件
      if (!this.isVerify) return null;
      // 验证上传文件大小
      if (this.fileMaxSize > 0 && file.size > this.fileMaxSize) {
        return `请上传${this.fileMaxSize / 1024 / 1024}MB大小以内的文件`;
      }
      // 验证上传文件类型
      if (this.fileTypes.length > 0 && !this.fileTypes.includes(file.type)) {
        return "暂不支持该类型文件";
      }
      //验证图片宽高
      return await this.checkImageWH(file);
    },
    //验证图片宽高
    checkImageWH(file) {
      return new Promise((resolve, reject) => {
        if (file.type.indexOf("image") === -1) {
          return resolve(null);
        }
        const _self = this;
        const filereader = new FileReader();
        filereader.onload = (e) => {
          try {
            const image = new Image();
            image.onload = function () {
              if (!_self.size) return resolve(null);
              if (_self.size.width && this.width > _self.size.width) {
                return reject(`请上传宽为${_self.size.width}的图片`);
              }
              if (_self.size.height && this.height > _self.size.height) {
                return reject(`请上传高为${_self.size.height}的图片`);
              }
              return resolve(null);
            };
            image.onerror = reject;
            image.src = e.target.result;
          } catch (error) {
            return reject(error);
          }
        };
        filereader.readAsDataURL(file);
      });
    },
    //自定义文件上传事件
    async handleCustomFileUpload(info) {
      try {
        if (this.maxnum > 0 && this.fileUrl.length >= this.maxnum) {
          this.$message.error(`最多充许上传${this.maxnum}个文件`);
          return false;
        }
        const formData = new FormData();
        formData.append(info.filename, info.file);
        const resData = await axios.request({
          url: this.uploadApiUrl,
          method: "POST",
          processData: false,
          data: formData,
        });
        if (resData.serveStatus === 200 && resData.status === 1) {
          info.file.url = URL.createObjectURL(info.file);
          this.handleSuccess(resData, info.file);
          return false;
        }
        return info.onError(resData.message);
      } catch (error) {
        return info.onError(error);
      }
    },
    //判断是否超过充许上传的文件数量
    handleExceed(files, fileList) {
      this.$message.error(`最多充许上传${this.maxnum}个文件`);
    },
  },
};
</script>

<style lang="scss" scoped>
::v-deep .el-upload--picture-card {
  line-height: initial;
  display: inline-table;
  padding: 8px;
}

.upload-text {
  height: 100%;
  width: 100%;
  display: table-cell;
  vertical-align: middle;
}
.thumb {
  width: 100%;
  height: 100%;
}
.multiple-loading {
  font-size: 32px;
  color: #8c939d;
  display: inline-block;
  margin: 50px;
}
</style>